/***********************************************************************
*  ipinnet - Check if IP is in network.
*  Copyright (C) 2020  CYBERDEViL
*
*  This file is part of ipinnet.
*
*  Searx-qt is free software: you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation, either version 3 of the License, or
*  (at your option) any later version.
*
*  ipinnet is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program.  If not, see <https://www.gnu.org/licenses/>.
*
***********************************************************************/

#include <string.h> // memset
#include <arpa/inet.h> // inet_aton, inet_pton
#include <stdlib.h> // exit, atoi
#include <math.h> // ceil

#include "ipinnet.h"

// https://linux.die.net/man/3/inet_aton
// https://linux.die.net/man/3/inet_pton
// https://man7.org/linux/man-pages/man7/ipv6.7.html


uint8_t addr_to_int(char * addr_str, uint32_t * addr) {
    struct in_addr addr_struct;
    if(inet_aton(addr_str, &addr_struct) == 0) {
        return 0;
    }
    memcpy(addr, &addr_struct.s_addr, sizeof(uint32_t));
    return 1;
}


uint8_t ipv6_addr_to_chararray(char * ipv6_addr_str, unsigned char * addr) {
    struct in6_addr addr_struct;
    if(inet_pton(AF_INET6, ipv6_addr_str, &addr_struct) == 0) {
        return 0;
    }
    memcpy(addr, &addr_struct.s6_addr, sizeof(unsigned char) * 16);
    return 1;
}


uint8_t addr_in_network(char * addr_str, char * net_str, uint8_t bits) {
    uint32_t addr = 0, *addr_ptr;
    addr_ptr = &addr;
    if(addr_to_int(addr_str, addr_ptr) == 0) {
        return 2;
    }

    uint32_t net_addr = 0, *net_ptr;
    net_ptr = &net_addr;
    if(addr_to_int(net_str, net_ptr) == 0) {
        return 3;
    }

    uint32_t bitmask = (uint32_t) 0xffffffff >> (32 - bits);
    uint32_t netmask = net_addr & bitmask;
    uint32_t addrmask = addr & bitmask;

    return (netmask == addrmask) ? (uint8_t) 0 : (uint8_t) 1;
}


uint8_t ipv6_addr_in_network(char * addr_str, char * net_str, uint8_t bits) {
    unsigned char ipv6_addr[16];
    (void) memset(ipv6_addr, 0, sizeof(unsigned char) * 16);
    if(ipv6_addr_to_chararray(addr_str, ipv6_addr) == 0) {
        return 2;
    }

    unsigned char ipv6_network_addr[16];
    (void) memset(ipv6_network_addr, 0, sizeof(unsigned char) * 16);
    if(ipv6_addr_to_chararray(net_str, ipv6_network_addr) == 0) {
        return 3;
    }

    uint8_t bytes_to_check = (uint8_t) ceil((double) bits / 8);

    for(uint8_t i = 0; i < bytes_to_check-1; i++) {
        if(ipv6_addr[i] != ipv6_network_addr[i]) {
            return 1;
        }
    }

    uint8_t ignore_bits = (128 - bits) % 8;

    return (ipv6_network_addr[bytes_to_check-1] >> ignore_bits ==
            ipv6_addr[bytes_to_check-1] >> ignore_bits)
           ? (uint8_t) 0 : (uint8_t) 1;
}
